﻿Imports System.Security.Principal

''' <summary>
''' グループリスト画面のフォームです。
''' </summary>
''' <remarks></remarks>
Friend NotInheritable Class GroupList

#Region " プライベートフィールド "
  'プライマリ グループのメンバー格納用(グループのプライマリ グループ トークン, メンバーのパスとオブジェクトの種類のコレクション)
  Private ReadOnly primaryGroupMembers As Dictionary(Of Integer, Dictionary(Of String, CategoryType))
  Private ReadOnly groupBindingSource As BindingSource
#End Region

#Region " パブリックコンストラクター "
  ''' <summary>
  ''' GroupList クラスの新しいインスタンスを初期化します。
  ''' </summary>
  ''' <remarks></remarks>
  Public Sub New()
    ' この呼び出しはデザイナーで必要です。
    InitializeComponent()
    ' InitializeComponent() 呼び出しの後で初期化を追加します。

    Dim groups As IEnumerable(Of IGroup)   'グループのリスト
    If DirectoryAccess.CanConnectDomain Then  'ドメインに接続できる時
      groups = DirectoryAccess.GetGroups(Of DomainGroup)()  'グループを取得
      groupBindingSource = Me.DomainGroupBindingSource
      Me.LocalPanel.Visible = False
    Else  'ドメインに接続できない時
      groups = DirectoryAccess.GetGroups(Of LocalGroup)() 'グループを取得
      groupBindingSource = Me.LocalGroupBindingSource
      Me.DomainPanel.Visible = False
      Me.GroupListBox.Height -= 96
      Me.Height -= 166
    End If
    primaryGroupMembers = New Dictionary(Of Integer, Dictionary(Of String, CategoryType))()
    groupBindingSource.DataSource = groups
    Me.DescriptionTextBox.DataBindings.Add("Text", groupBindingSource, "Description")
    Me.GroupListBox.DataSource = groupBindingSource
    Me.CountLabel.Text = String.Format("{0} 個のオブジェクト", groupBindingSource.Count)
  End Sub
#End Region

#Region " イベントハンドラ "
  Private Sub GroupListBox_SelectedIndexChanged(sender As Object, e As EventArgs) Handles GroupListBox.SelectedIndexChanged
    If Me.GroupListBox.SelectedIndex = -1 Then
      Return
    End If

    Me.ShowRelatingInfo()   '関連情報を表示
    Me.InRoleButton.Enabled = Me.GroupListView.Items.Count > 0
    Me.MemberButton.Enabled = Me.MemberListView.Items.Count > 0
  End Sub

  Private Sub InRoleButton_Click(sender As Object, e As EventArgs) Handles InRoleButton.Click
    Dim group = DirectCast(groupBindingSource.Current, DomainGroup)   '選択されたグループ
    Using fm As New InRoleGroupList(group)  '所属している全グループ画面
      fm.ShowDialog(Me)
    End Using
  End Sub

  Private Sub MemberButton_Click(sender As Object, e As EventArgs) Handles MemberButton.Click
    Dim group = DirectCast(groupBindingSource.Current, DomainGroup)   '選択されたグループ
    Using fm As New AllMemberList(group)  'すべてのメンバー画面
      fm.ShowDialog(Me)
    End Using
  End Sub
#End Region

#Region " プライベートメソッド "
  ''' <summary>
  ''' 関連情報を表示します。
  ''' </summary>
  ''' <remarks></remarks>
  Private Sub ShowRelatingInfo()
    Dim group = DirectCast(groupBindingSource.Current, IGroup)    '選択されたグループ
    If DirectoryAccess.CanConnectDomain Then  'ドメインに接続できる時
      Me.ShowDomainMembers(group.Native)  'ドメイングループのメンバーを表示
      Me.ShowBelongGroups(group.Entry)    '所属するグループを表示
    Else  'ドメインに接続できない時
      Me.ShowLocalMembers(group.Native)   'ローカルグループのメンバーを表示
    End If
  End Sub

  ''' <summary>
  ''' ドメイングループのメンバーを表示します。
  ''' </summary>
  ''' <param name="group">グループ。</param>
  ''' <remarks></remarks>
  Private Sub ShowDomainMembers(group As IADsGroup)
    Dim subItem(1) As String    '名前と所属パスの配列
    Dim objectType As CategoryType    'ディレクトリ オブジェクトの種類

    Me.MemberListView.Items.Clear()
    For Each member As IADs In group.Members()
      objectType = DirectCast([Enum].Parse(GetType(CategoryType), member.Class, True), CategoryType)
      If objectType = CategoryType.ForeignSecurityPrincipal Then  '外部のセキュリティ プリンシパルの時
        Dim objectSid = DirectCast(member.Get("objectSid"), Byte())
        Dim sid = New SecurityIdentifier(objectSid, 0)  'SID
        Dim account = DirectCast(sid.Translate(GetType(NTAccount)), NTAccount)  'アカウントに変換
        subItem(0) = account.Value  'NT AUTHORITY\○○ => ToStringメソッドでも同じ
      Else  '外部のセキュリティプリンシパル以外の時
        subItem(0) = member.Get("cn").ToString()
      End If
      subItem(1) = DirectoryAccess.GetBelongPath(member)    '所属パスを取得
      Me.MemberListView.Items.Add(New ListViewItem(subItem, objectType))  'リストビューに項目を追加
    Next
    Me.AddPrimaryGroupMembers()   'プライマリ グループになっているメンバーを追加
  End Sub

  ''' <summary>
  ''' プライマリ グループになっているメンバーを追加します。
  ''' </summary>
  ''' <remarks></remarks>
  Private Sub AddPrimaryGroupMembers()
    Dim token = DirectCast(groupBindingSource.Current, DomainGroup).Token   '選択されたグループのプライマリ グループ トークン
    'プライマリ グループのメンバー(パスとオブジェクトの種類のコレクション)
    Dim primaryMembers As Dictionary(Of String, CategoryType) = Nothing

    If primaryGroupMembers.TryGetValue(token, primaryMembers) Then  '格納済の時
      If primaryMembers.Count = 0 Then
        Return
      End If
    Else    '未格納の時
      'プライマリ グループ トークンのグループをプライマリ グループとしているメンバーのDirectoryEntryのコレクションを取得
      Dim primaryMemberEntries = DirectoryAccess.GetPrimaryGroupMemberEntries(token)
      primaryMembers = New Dictionary(Of String, CategoryType)(primaryMemberEntries.Count)
      primaryGroupMembers.Add(token, primaryMembers)
      If primaryMemberEntries.Count = 0 Then
        Return
      End If

      For Each user In primaryMemberEntries   '取得メンバー数分
        primaryMembers.Add(user.Path,
          DirectCast([Enum].Parse(GetType(CategoryType), user.SchemaClassName, True), CategoryType))
        user.Close()
      Next
    End If

    Dim subItem(1) As String    '名前と所属パスの配列
    For Each member In primaryMembers   'メンバー数分
      subItem(0) = DirectoryAccess.PathToCn(member.Key)   '名前を取得
      subItem(1) = DirectoryAccess.GetBelongPath(member.Key)    '所属パスを取得
      Me.MemberListView.Items.Add(New ListViewItem(subItem, member.Value))  'リストビューに項目を追加
    Next
  End Sub

  ''' <summary>
  ''' 所属するグループを表示します。
  ''' </summary>
  ''' <param name="group">グループ。</param>
  ''' <remarks></remarks>
  Private Sub ShowBelongGroups(group As DirectoryEntry)
    Dim subItem(1) As String      '名前と所属パスの配列
    Me.GroupListView.Items.Clear()
    For Each groupPath As String In group.Properties.Item("memberOf")
      subItem(0) = DirectoryAccess.PathToCn(groupPath)  '名前を取得
      subItem(1) = DirectoryAccess.GetBelongPath(groupPath)   '所属パスを取得
      Me.GroupListView.Items.Add(New ListViewItem(subItem))   'リストビューに項目を追加
    Next
  End Sub

  ''' <summary>
  ''' ローカルグループのメンバーを表示します。
  ''' </summary>
  ''' <param name="group">グループ。</param>
  ''' <remarks></remarks>
  Private Sub ShowLocalMembers(group As IADsGroup)
    Dim members = group.Members().Cast(Of IADs)().Select(Function(member) GetLocalMemberName(member)).ToList()
    Me.MemberListBox.DataSource = members
  End Sub

  ''' <summary>
  ''' ローカルのメンバー名を取得します。
  ''' </summary>
  ''' <param name="member">メンバー。</param>
  ''' <returns>メンバー名。</returns>
  ''' <remarks></remarks>
  Private Function GetLocalMemberName(member As IADs) As String
    Dim eidx = member.ADsPath.LastIndexOf("/"c) - 1
    Dim sidx = member.ADsPath.LastIndexOf("/"c, eidx) + 1
    Dim pcname = member.ADsPath.Substring(sidx, eidx - sidx + 1)

    If pcname.Equals(Environment.UserDomainName, StringComparison.CurrentCultureIgnoreCase) Then  'ローカルユーザーの時
      Return member.Name
    Else  'ローカルユーザー以外の時
      Return member.ADsPath.Substring(sidx).Replace("/"c, "\"c)
    End If
  End Function
#End Region
End Class